package datastore.db;

import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

import datastore.gqlparser.ParsedQuery;
import datastore.gqlparser.javacc.GqlParser;

/*
 * GQL Reference

GQL is a SQL-like language for retrieving data entities from the App Engine scalable datastore. While GQL's features are different from those of a query language for a traditional relational database, the GQL syntax is similar to that of SQL.

The GQL syntax can be summarized as follows:

  SELECT * FROM <kind>
    [WHERE <condition> [AND <condition> ...]]
    [ORDER BY <property> [ASC | DESC] [, <property> [ASC | DESC] ...]]
    [LIMIT [<offset>,]<count>]
    [OFFSET <offset>]

  <condition> := <property> {< | <= | > | >= | = | != } <value>
  <condition> := <property> IN <list>
  <condition> := ANCESTOR IS <entity or key>

As with SQL, GQL keywords are case insensitive. Kind and property names are case sensitive.

A GQL query returns zero or more data entities of the requested kind, as instances of the kind's model class. A result is always a complete entity, so every GQL query always begins with SELECT * FROM followed by the name of the kind. (The SQL-like field specifier is always *. The SQL syntax is retained for familiarity.) A GQL query cannot perform a SQL-like "join" query.

The optional WHERE clause filters the result set to those entities that meet one or more conditions. Each condition compares a property of the entity with a value using a comparison operator. If multiple conditions are given with the AND keyword, then an entity must meet all of the conditions to be returned by the query. GQL does not have an OR operator. However, it does have an IN operator, which provides a limited form of OR.

The IN operator compares value of a property to each item in a list. The IN operator is equivalent to many = queries, one for each value, that are ORed together. An entity whose value for the given property equals any of the values in the list can be returned for the query.

Note: The IN and != operators use multiple queries behind the scenes. For example, the IN operator executes a separate underlying datastore query for every item in the list. The entities returned are a result of the cross-product of all the underlying datastore queries and are de-duplicated. A maximum of 30 datastore queries are allowed for any single GQL query.

A condition can also test whether an entity has a given entity as an ancestor, using the ANCESTOR IS operator. The value is a model instance or Key for the ancestor entity. For more information on ancestors, see Keys and Entity Groups.

The left-hand side of a comparison is always a property name. The right-hand side can be one of the following (as appropriate for the property's data type):

    * a str literal, as a single-quoted string. Single-quote characters in the string must be escaped as ''. For example: 'Joe''s Diner'
    * an integer or floating point number literal. For example: 42.7
    * a Boolean literal, as TRUE or FALSE.
    * a datetime, date, or time literal, with either numeric values or a string representation, in the following forms:
          o DATETIME(year, month, day, hour, minute, second)
          o DATETIME('YYYY-MM-DD HH:MM:SS')
          o DATE(year, month, day)
          o DATE('YYYY-MM-DD')
          o TIME(hour, minute, second)
          o TIME('HH:MM:SS')
    * an entity key literal, with either a string-encoded key or a complete path of kinds and key names/IDs:
          o KEY('encoded key')
          o KEY(kind, name/ID [, kind, name/ID...])
    * a User object literal, with the user's email address:
      USER(email-address)
    * a GeoPt literal, with the latitude and longitude as floating point values:
      GEOPT(lat, long)
    * a bound parameter value. In the query string, positional parameters are referenced by number: title = :1 Keyword parameters are referenced by name: title = :mytitle

Bound parameters can be bound as positional arguments or keyword arguments passed to the GqlQuery constructor or a Model class's gql() method. Property data types that do not have corresponding value literal syntax must be specified using parameter binding, including the list data type. Parameter bindings can be re-bound with new values during the lifetime of the GqlQuery instance (such as to efficiently reuse a query) using the bind() method.

The optional ORDER BY clause indicates that results should be returned sorted by the given properties, in either ascending (ASC) or descending (DESC) order. If the direction is not specified, it defaults to ASC. The ORDER BY clause can specify multiple sort orders as a comma-delimited list, evaluated from left to right.

An optional LIMIT clause causes the query to stop returning results after the first count entities. The LIMIT can also include an offset to skip that many results to find the first result to return. An optional OFFSET clause can specify an offset if no LIMIT clause is present.

Note: A LIMIT clause has a maximum of 1000. If a limit larger than the maximum is specified, the maximum is used. This same maximum applies to the fetch() method of the GqlQuery class.

Note: Like the offset parameter for the fetch() method, an OFFSET in a GQL query string does not reduce the number of entities fetched from the datastore. It only affects which results are returned by the fetch() method. A query with an offset has performance characteristics that correspond linearly with the offset size.

For information on executing GQL queries, binding parameters, and accessing results, see the GqlQuery class, and the Model.gql() class method.

GQL is a SQL-like query language suitable for querying the App Engine datastore. For a complete discussion of the GQL syntax and features, see the GQL Reference.

The GqlQuery constructor takes as an argument a complete GQL statement beginning with SELECT * FROM model-name. Values in WHERE clauses can be string or number literals, or can use parameter binding for values. Bound parameters can be bound initially using positional or keyword arguments to the constructor.

query = GqlQuery("SELECT * FROM Song WHERE composer = 'Lennon, John'")

query = GqlQuery("SELECT * FROM Song WHERE composer = :1", "Lennon, John")

query = GqlQuery("SELECT * FROM Song WHERE composer = :composer", composer="Lennon, John")

For convenience, Model and Expando classes have a gql() method that returns a GqlQuery instance. This method takes a GQL query string without the SELECT * FROM model-name, which is implied.

query = Song.gql("WHERE composer = 'Lennon, John'")

As with the Query class, the application executes the query and accesses results either by calling the fetch() method, or by treating the GqlQuery object as an iterable. See the Query documentation for more information.

There is one difference between how Query and GqlQuery access results: If the GQL query includes a LIMIT clause or an OFFSET clause, results are retrieved as with the equivalent fetch() method, even if the iterator interface is used to access the results. When a GqlQuery whose GQL contains LIMIT or OFFSET is used as an iterable, one call is made to the datastore to fetch all of the results, and the iterator returns each of the results from memory.

for song in q:
  print song.title

See also Query, a query class that uses objects and methods to prepare queries instead of GQL.

Note: The index-based data structures and algorithms that power datastore queries do not support some kinds of queries. See Queries and Indexes: Restrictions on Queries for more information.
*/
public class GqlQuery<M extends Model> extends BaseQuery<M> {
    private String queryString;
    private Map<String, Object> args;
    private ParsedQuery query;

    /**
     * Constructor
     * 
     * The constructor of the GqlQuery class is defined as follows:
     * 
     * class GqlQuery(query_string,args,kwds)
     * 
     * A query object using the App Engine query language GQL.
     * 
     * Arguments:
     * 
     * query_string A complete GQL statement beginning with SELECT FROM model-name.args Positional parameter bindings.
     * kwds Keyword parameter bindings.
     * @throws Exception 
     */
    public GqlQuery(Class<M> modelClass, String query, Map<String, Object> args) throws Exception {
        super(modelClass);
        this.queryString = query;
        this.args = args;
        this.query = new GqlParser(new StringReader(queryString)).parse();
        bind(args);
    }

    public GqlQuery(Class<M> modelClass, String query, Object ... args) throws Exception {
        this(modelClass, query, getMap(args));
    }

    /**
     * Instance Methods
     * 
     * A GqlQuery instance has the following methods:
     * 
     * bind(*args, **kwds)
     * 
     * Re-binds parameters for the query. The new query is executed the first time results are accessed after parameters
     * have been re-bound.
     * 
     * Re-using the GqlQuery object with new parameters is faster than building a new GqlQuery object because re-binding
     * does not require that the query string be parsed again.
     * 
     * Arguments:
     * 
     *args The new positional parameter bindings. kwds The new keyword parameter bindings.
     */
    public void bind(Map<String, Object> args) {
        throw new UnsupportedOperationException();
    }
    public void bind(Object ... args) {
        bind(getMap(args));
    }

    private static Map<String, Object> getMap(Object ... args) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < args.length; i++) {
            map.put(String.valueOf(i+1), args[i]);
        }
        return map;
    }

}
